/* UNIX V7 source code: see /COPYRIGHT or www.tuhs.org for details. */

#include <string.h>
#include "dextern.h"
#include "prototype.h"

#define IDENTIFIER 257
#define MARK 258
#define TERM 259
#define LEFT 260
#define RIGHT 261
#define BINARY 262
#define PREC 263
#define LCURLY 264
#define C_IDENTIFIER 265	/* name followed by colon */
#define NUMBER 266
#define START 267
#define TYPEDEF 268
#define TYPENAME 269
#define UNION 270
#define ENDFILE 0

	/* communication variables between various I/O routines */

char *infile;			/* input file name */
int numbval;			/* value of an input number */
char tokname[NAMESIZE];		/* input token name */

	/* storage of names */

char cnames[CNAMSZ];		/* place where token and nonterminal names are stored */
int cnamsz = CNAMSZ;		/* size of cnames */
char *cnamp = cnames;		/* place where next name is to be put in */
int ndefout = 3;		/* number of defined symbols output */

	/* storage of types */
int ntypes;			/* number of types defined */
char *typeset[NTYPES];		/* pointers to type tags */

	/* symbol tables for tokens and nonterminals */

int ntokens = 0;
struct toksymb tokset[NTERMS];
int toklev[NTERMS];
int nnonter = -1;
struct ntsymb nontrst[NNONTERM];
int start;			/* start symbol */

	/* assigned token type values */
int extval = 0;

	/* input and output file descriptors */

FILE *finput;			/* yacc input file */
FILE *faction;			/* file for saving actions */
FILE *fdefine;			/* file for # defines */
FILE *ftable;			/* y.tab.c file */
FILE *ftemp;			/* tempfile to pass 2 */
FILE *foutput;			/* y.output file */

	/* storage for grammar rules */

int mem0[MEMSIZE];		/* production storage */
int *mem = mem0;
int nprod = 1;			/* number of productions */
int *prdptr[NPROD];		/* pointers to descriptions of productions */
int levprd[NPROD];		/* precedence levels for the productions */


void setup(int argc, char *argv[])
{
	int i, j, lev, t, ty;
	int c;
	int *p;
	char actname[8];

	foutput = NULL;
	fdefine = NULL;
	i = 1;
	while (argc >= 2 && argv[1][0] == '-') {
		while (*++(argv[1])) {
			switch (*argv[1]) {
			case 'v':
			case 'V':
				foutput = fopen(FILEU, "w");
				if (foutput == NULL)
					error("cannot open y.output");
				continue;
			case 'D':
			case 'd':
				fdefine = fopen(FILED, "w");
				continue;
			case 'o':
			case 'O':
				fprintf(stderr, "`o' flag now default in yacc\n");
				continue;

			case 'r':
			case 'R':
				error("Ratfor Yacc is dead: sorry...\n");

			default:
				error("illegal option: %c", *argv[1]);
			}
		}
		argv++;
		argc--;
	}

	ftable = fopen(OFILE, "w");
	if (ftable == NULL)
		error("cannot open table file");

	ftemp = fopen(TEMPNAME, "w");
	faction = fopen(ACTNAME, "w");
	if (ftemp == NULL || faction == NULL)
		error("cannot open temp file");

	if (argc < 2 || ((finput = fopen(infile = argv[1], "r")) == NULL)) {
		error("cannot open input file");
	}

	cnamp = cnames;
	defin(0, "$end");
	extval = 0400;
	defin(0, "error");
	defin(1, "$accept");
	mem = mem0;
	lev = 0;
	ty = 0;
	i = 0;

	/* sorry -- no yacc parser here.....
	   we must bootstrap somehow... */

	for (t = gettok(); t != MARK && t != ENDFILE;) {
		switch (t) {

		case ';':
			t = gettok();
			break;

		case START:
			if ((t = gettok()) != IDENTIFIER) {
				error("bad %%start construction");
			}
			start = chfind(1, tokname);
			t = gettok();
			continue;

		case TYPEDEF:
			if ((t = gettok()) != TYPENAME)
				error("bad syntax in %%type");
			ty = numbval;
			for (;;) {
				t = gettok();
				switch (t) {

				case IDENTIFIER:
					if ((t = chfind(1, tokname)) < NTBASE) {
						j = TYPE(toklev[t]);
						if (j != 0 && j != ty) {
							error("type redeclaration of token %s", tokset[t].name);
						} else
							SETTYPE(toklev[t], ty);
					} else {
						j = nontrst[t - NTBASE].tvalue;
						if (j != 0 && j != ty) {
							error("type redeclaration of nonterminal %s", nontrst[t - NTBASE].name);
						} else
							nontrst[t - NTBASE].tvalue = ty;
					}
				case ',':
					continue;

				case ';':
					t = gettok();
					break;
				default:
					break;
				}
				break;
			}
			continue;

		case UNION:
			/* copy the union declaration to the output */
			cpyunion();
			t = gettok();
			continue;

		case LEFT:
		case BINARY:
		case RIGHT:
			++i;
		case TERM:
			lev = t - TERM;	/* nonzero means new prec. and assoc. */
			ty = 0;

			/* get identifiers so defined */

			t = gettok();
			if (t == TYPENAME) {	/* there is a type defined */
				ty = numbval;
				t = gettok();
			}

			for (;;) {
				switch (t) {

				case ',':
					t = gettok();
					continue;

				case ';':
					break;

				case IDENTIFIER:
					j = chfind(0, tokname);
					if (lev) {
						if (ASSOC(toklev[j]))
							error("redeclaration of precedence of %s", tokname);
						SETASC(toklev[j], lev);
						SETPLEV(toklev[j], i);
					}
					if (ty) {
						if (TYPE(toklev[j]))
							error("redeclaration of type of %s", tokname);
						SETTYPE(toklev[j], ty);
					}
					if ((t = gettok()) == NUMBER) {
						tokset[j].value = numbval;
						if (j < ndefout && j > 2) {
							error("please define type number of %s earlier", tokset[j].name);
						}
						t = gettok();
					}
					continue;

				}

				break;
			}

			continue;

		case LCURLY:
			defout();
			cpycode();
			t = gettok();
			continue;

		default:
			error("syntax error");

		}

	}

	if (t == ENDFILE) {
		error("unexpected EOF before %%");
	}

	/* t is MARK */

	defout();

	fprintf(ftable, "#define yyclearin yychar = -1\n");
	fprintf(ftable, "#define yyerrok yyerrflag = 0\n");
	fprintf(ftable, "extern int yychar;\nextern short yyerrflag;\n");
	fprintf(ftable, "#ifndef YYMAXDEPTH\n#define YYMAXDEPTH 150\n#endif\n");
	if (!ntypes)
		fprintf(ftable, "#ifndef YYSTYPE\n#define YYSTYPE int\n#endif\n");
	fprintf(ftable, "YYSTYPE yylval, yyval;\n");

	prdptr[0] = mem;
	/* added production */
	*mem++ = NTBASE;
	*mem++ = start;		/* if start is 0, we will overwrite with the lhs of the first rule */
	*mem++ = 1;
	*mem++ = 0;
	prdptr[1] = mem;

	while ((t = gettok()) == LCURLY)
		cpycode();

	if (t != C_IDENTIFIER)
		error("bad syntax on first rule");

	if (!start)
		prdptr[0][1] = chfind(1, tokname);

	/* read rules */

	while (t != MARK && t != ENDFILE) {

		/* process a rule */

		if (t == '|') {
			*mem++ = *prdptr[nprod - 1];
		} else if (t == C_IDENTIFIER) {
			*mem = chfind(1, tokname);
			if (*mem < NTBASE)
				error("token illegal on LHS of grammar rule");
			++mem;
		} else
			error("illegal rule: missing semicolon or | ?");

		/* read rule body */


		t = gettok();
	      more_rule:
		while (t == IDENTIFIER) {
			*mem = chfind(1, tokname);
			if (*mem < NTBASE)
				levprd[nprod] = toklev[*mem];
			++mem;
			t = gettok();
		}


		if (t == PREC) {
			if (gettok() != IDENTIFIER)
				error("illegal %%prec syntax");
			j = chfind(2, tokname);
			if (j >= NTBASE)
				error("nonterminal %s illegal after %%prec", nontrst[j - NTBASE].name);
			levprd[nprod] = toklev[j];
			t = gettok();
		}

		if (t == '=') {
			levprd[nprod] |= ACTFLAG;
			fprintf(faction, "\ncase %d:", nprod);
			cpyact(mem - prdptr[nprod] - 1);
			fprintf(faction, " break;");
			if ((t = gettok()) == IDENTIFIER) {
				/* action within rule... */

				sprintf(actname, "$$%d", nprod);
				j = chfind(1, actname);	/* make it a nonterminal */

				/* the current rule will become rule number nprod+1 */
				/* move the contents down, and make room for the null */

				for (p = mem; p >= prdptr[nprod]; --p)
					p[2] = *p;
				mem += 2;

				/* enter null production for action */

				p = prdptr[nprod];

				*p++ = j;
				*p++ = -nprod;

				/* update the production information */

				levprd[nprod + 1] = levprd[nprod] & ~ACTFLAG;
				levprd[nprod] = ACTFLAG;

				if (++nprod >= NPROD)
					error("more than %d rules", NPROD);
				prdptr[nprod] = p;

				/* make the action appear in the original rule */
				*mem++ = j;

				/* get some more of the rule */

				goto more_rule;
			}

		}

		while (t == ';')
			t = gettok();

		*mem++ = -nprod;

		/* check that default action is reasonable */

		if (ntypes && !(levprd[nprod] & ACTFLAG) && nontrst[*prdptr[nprod] - NTBASE].tvalue) {
			/* no explicit action, LHS has value */
			int tempty;
			tempty = prdptr[nprod][1];
			if (tempty < 0)
				error("must return a value, since LHS has a type");
			else if (tempty >= NTBASE)
				tempty = nontrst[tempty - NTBASE].tvalue;
			else
				tempty = TYPE(toklev[tempty]);
			if (tempty != nontrst[*prdptr[nprod] - NTBASE].tvalue) {
				error("default action causes potential type clash");
			}
		}

		if (++nprod >= NPROD)
			error("more than %d rules", NPROD);
		prdptr[nprod] = mem;
		levprd[nprod] = 0;

	}

	/* end of all rules */

	finact();
	if (t == MARK) {
		fprintf(ftable, "\n# line %d \"%s\"\n", lineno, infile);
		while ((c = getc(finput)) != EOF)
			putc(c, ftable);
	}
	fclose(finput);
}

void finact(void)
{
	/* finish action routine */

	fclose(faction);

	fprintf(ftable, "# define YYERRCODE %d\n", tokset[2].value);

}

int defin(int t, char *s)
{
/*	define s to be a terminal if t=0
	or a nonterminal if t=1		*/

	int val;

	if (t) {
		if (++nnonter >= NNONTERM)
			error("too many nonterminals, limit %d", NNONTERM);
		nontrst[nnonter].name = cstash(s);
		return (NTBASE + nnonter);
	}
	/* must be a token */
	if (++ntokens >= NTERMS)
		error("too many terminals, limit %d", NTERMS);
	tokset[ntokens].name = cstash(s);

	/* establish value for token */

	if (s[0] == ' ' && s[2] == '\0')	/* single character literal */
		val = s[1];
	else if (s[0] == ' ' && s[1] == '\\') {	/* escape sequence */
		if (s[3] == '\0') {	/* single character escape sequence */
			switch (s[2]) {
				/* character which is escaped */
			case 'n':
				val = '\n';
				break;
			case 'r':
				val = '\r';
				break;
			case 'b':
				val = '\b';
				break;
			case 't':
				val = '\t';
				break;
			case 'f':
				val = '\f';
				break;
			case '\'':
				val = '\'';
				break;
			case '"':
				val = '"';
				break;
			case '\\':
				val = '\\';
				break;
			default:
				error("invalid escape");
			}
		} else if (s[2] <= '7' && s[2] >= '0') {	/* \nnn sequence */
			if (s[3] < '0' || s[3] > '7' || s[4] < '0' || s[4] > '7' || s[5] != '\0')
				error("illegal \\nnn construction");
			val = 64 * s[2] + 8 * s[3] + s[4] - 73 * '0';
			if (val == 0)
				error("'\\000' is illegal");
		}
	} else {
		val = extval++;
	}
	tokset[ntokens].value = val;
	toklev[ntokens] = 0;
	return (ntokens);
}

void defout(void)
{				/* write out the defines (at the end of the declaration section) */

	int i, c;
	char *cp;

	for (i = ndefout; i <= ntokens; ++i) {

		cp = tokset[i].name;
		if (*cp == ' ')
			++cp;	/* literals */

		for (; (c = *cp) != '\0'; ++cp) {

			if (islower(c) || isupper(c) || isdigit(c) || c == '_');	/* VOID */
			else
				goto nodef;
		}

		fprintf(ftable, "# define %s %d\n", tokset[i].name, tokset[i].value);
		if (fdefine != NULL)
			fprintf(fdefine, "# define %s %d\n", tokset[i].name, tokset[i].value);

	      nodef:;
	}

	ndefout = ntokens + 1;

}

char *cstash(char *s)
{
	char *temp;

	temp = cnamp;
	do {
		if (cnamp >= &cnames[cnamsz])
			error("too many characters in id's and literals");
		else
			*cnamp++ = *s;
	} while (*s++);
	return (temp);
}

int gettok(void)
{
	int i, base;
	static int peekline;	/* number of '\n' seen in lookahead */
	int c, match, reserve;

      begin:
	reserve = 0;
	lineno += peekline;
	peekline = 0;
	c = getc(finput);
	while (c == ' ' || c == '\n' || c == '\t' || c == '\f') {
		if (c == '\n')
			++lineno;
		c = getc(finput);
	}
	if (c == '/') {		/* skip comment */
		lineno += skipcom();
		goto begin;
	}

	switch (c) {

	case EOF:
		return (ENDFILE);
	case '{':
		ungetc(c, finput);
		return ('=');	/* action ... */
	case '<':		/* get, and look up, a type name (union member name) */
		i = 0;
		while ((c = getc(finput)) != '>' && c >= 0 && c != '\n') {
			tokname[i] = c;
			if (++i >= NAMESIZE)
				--i;
		}
		if (c != '>')
			error("unterminated < ... > clause");
		tokname[i] = '\0';
		for (i = 1; i <= ntypes; ++i) {
			if (!strcmp(typeset[i], tokname)) {
				numbval = i;
				return (TYPENAME);
			}
		}
		typeset[numbval = ++ntypes] = cstash(tokname);
		return (TYPENAME);

	case '"':
	case '\'':
		match = c;
		tokname[0] = ' ';
		i = 1;
		for (;;) {
			c = getc(finput);
			if (c == '\n' || c == EOF)
				error("illegal or missing ' or \"");
			if (c == '\\') {
				c = getc(finput);
				tokname[i] = '\\';
				if (++i >= NAMESIZE)
					--i;
			} else if (c == match)
				break;
			tokname[i] = c;
			if (++i >= NAMESIZE)
				--i;
		}
		break;

	case '%':
	case '\\':

		switch (c = getc(finput)) {

		case '0':
			return (TERM);
		case '<':
			return (LEFT);
		case '2':
			return (BINARY);
		case '>':
			return (RIGHT);
		case '%':
		case '\\':
			return (MARK);
		case '=':
			return (PREC);
		case '{':
			return (LCURLY);
		default:
			reserve = 1;
		}

	default:

		if (isdigit(c)) {	/* number */
			numbval = c - '0';
			base = (c == '0') ? 8 : 10;
			for (c = getc(finput); isdigit(c); c = getc(finput)) {
				numbval = numbval * base + c - '0';
			}
			ungetc(c, finput);
			return (NUMBER);
		} else if (islower(c) || isupper(c) || c == '_' || c == '.' || c == '$') {
			i = 0;
			while (islower(c) || isupper(c) || isdigit(c) || c == '_' || c == '.' || c == '$') {
				tokname[i] = c;
				if (reserve && isupper(c))
					tokname[i] += 'a' - 'A';
				if (++i >= NAMESIZE)
					--i;
				c = getc(finput);
			}
		} else
			return (c);

		ungetc(c, finput);
	}

	tokname[i] = '\0';

	if (reserve) {		/* find a reserved word */
		if (!strcmp(tokname, "term"))
			return (TERM);
		if (!strcmp(tokname, "token"))
			return (TERM);
		if (!strcmp(tokname, "left"))
			return (LEFT);
		if (!strcmp(tokname, "nonassoc"))
			return (BINARY);
		if (!strcmp(tokname, "binary"))
			return (BINARY);
		if (!strcmp(tokname, "right"))
			return (RIGHT);
		if (!strcmp(tokname, "prec"))
			return (PREC);
		if (!strcmp(tokname, "start"))
			return (START);
		if (!strcmp(tokname, "type"))
			return (TYPEDEF);
		if (!strcmp(tokname, "union"))
			return (UNION);
		error("invalid escape, or illegal reserved word: %s", tokname);
	}

	/* look ahead to distinguish IDENTIFIER from C_IDENTIFIER */

	c = getc(finput);
	while (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '/') {
		if (c == '\n')
			++peekline;
		else if (c == '/') {	/* look for comments */
			peekline += skipcom();
		}
		c = getc(finput);
	}
	if (c == ':')
		return (C_IDENTIFIER);
	ungetc(c, finput);
	return (IDENTIFIER);
}

int fdtype(int t)
{				/* determine the type of a symbol */
	int v;
	if (t >= NTBASE)
		v = nontrst[t - NTBASE].tvalue;
	else
		v = TYPE(toklev[t]);
	if (v <= 0)
		error("must specify type for %s", (t >= NTBASE) ? nontrst[t - NTBASE].name : tokset[t].name);
	return (v);
}

int chfind(int t, char *s)
{
	int i;

	if (s[0] == ' ')
		t = 0;
	TLOOP(i) {
		if (!strcmp(s, tokset[i].name)) {
			return (i);
		}
	}
	NTLOOP(i) {
		if (!strcmp(s, nontrst[i].name)) {
			return (i + NTBASE);
		}
	}
	/* cannot find name */
	if (t > 1)
		error("%s should have been defined earlier", s);
	return (defin(t, s));
}

void cpyunion(void)
{
	/* copy the union declaration to the output, and the define file if present */

	int level, c;
	fprintf(ftable, "\n# line %d \"%s\"\n", lineno, infile);
	fprintf(ftable, "typedef union ");
	if (fdefine)
		fprintf(fdefine, "\ntypedef union ");

	level = 0;
	for (;;) {
		if ((c = getc(finput)) < 0)
			error("EOF encountered while processing %%union");
		putc(c, ftable);
		if (fdefine)
			putc(c, fdefine);

		switch (c) {

		case '\n':
			++lineno;
			break;

		case '{':
			++level;
			break;

		case '}':
			--level;
			if (level == 0) {	/* we are finished copying */
				fprintf(ftable, " YYSTYPE;\n");
				if (fdefine)
					fprintf(fdefine, " YYSTYPE;\nextern YYSTYPE yylval;\n");
				return;
			}
		}
	}
}

void cpycode(void)
{				/* copies code between \{ and \} */

	int c;
	c = getc(finput);
	if (c == '\n') {
		c = getc(finput);
		lineno++;
	}
	fprintf(ftable, "\n# line %d \"%s\"\n", lineno, infile);
	while (c >= 0) {
		if (c == '\\') {
			if ((c = getc(finput)) == '}')
				return;
			else
				putc('\\', ftable);
		}
		if (c == '%') {
			if ((c = getc(finput)) == '}')
				return;
			else
				putc('%', ftable);
		}
		putc(c, ftable);
		if (c == '\n')
			++lineno;
		c = getc(finput);
	}
	error("eof before %%}");
}

int skipcom(void)
{				/* skip over comments */
	int c, i = 0;		/* i is the number of lines skipped */

	/* skipcom is called after reading a / */

	if (getc(finput) != '*')
		error("illegal comment");
	c = getc(finput);
	while (c != EOF) {
		while (c == '*') {
			if ((c = getc(finput)) == '/')
				return (i);
		}
		if (c == '\n')
			++i;
		c = getc(finput);
	}
	error("EOF inside comment");
	/* NOTREACHED */
}

void cpyact(int offset)
{				/* copy C action to the next ; or closing } */
	int brac, c, match, j, s, tok;

	fprintf(faction, "\n# line %d \"%s\"\n", lineno, infile);

	brac = 0;

      loop:
	c = getc(finput);
      swt:
	switch (c) {

	case ';':
		if (brac == 0) {
			putc(c, faction);
			return;
		}
		goto lcopy;

	case '{':
		brac++;
		goto lcopy;

	case '$':
		s = 1;
		tok = -1;
		c = getc(finput);
		if (c == '<') {	/* type description */
			ungetc(c, finput);
			if (gettok() != TYPENAME)
				error("bad syntax on $<ident> clause");
			tok = numbval;
			c = getc(finput);
		}
		if (c == '$') {
			fprintf(faction, "yyval");
			if (ntypes) {	/* put out the proper tag... */
				if (tok < 0)
					tok = fdtype(*prdptr[nprod]);
				fprintf(faction, ".%s", typeset[tok]);
			}
			goto loop;
		}
		if (c == '-') {
			s = -s;
			c = getc(finput);
		}
		if (isdigit(c)) {
			j = 0;
			while (isdigit(c)) {
				j = j * 10 + c - '0';
				c = getc(finput);
			}

			j = j * s - offset;
			if (j > 0) {
				error("Illegal use of $%d", j + offset);
			}

			fprintf(faction, "yypvt[-%d]", -j);
			if (ntypes) {	/* put out the proper tag */
				if (j + offset <= 0 && tok < 0)
					error("must specify type of $%d", j + offset);
				if (tok < 0)
					tok = fdtype(prdptr[nprod][j + offset]);
				fprintf(faction, ".%s", typeset[tok]);
			}
			goto swt;
		}
		putc('$', faction);
		if (s < 0)
			putc('-', faction);
		goto swt;

	case '}':
		if (--brac)
			goto lcopy;
		putc(c, faction);
		return;


	case '/':		/* look for comments */
		putc(c, faction);
		c = getc(finput);
		if (c != '*')
			goto swt;

		/* it really is a comment */

		putc(c, faction);
		c = getc(finput);
		while (c != EOF) {
			while (c == '*') {
				putc(c, faction);
				if ((c = getc(finput)) == '/')
					goto lcopy;
			}
			putc(c, faction);
			if (c == '\n')
				++lineno;
			c = getc(finput);
		}
		error("EOF inside comment");

	case '\'':		/* character constant */
		match = '\'';
		goto string;

	case '"':		/* character string */
		match = '"';

	      string:

		putc(c, faction);
		/* FIXME 0 or EOF ??? */
		while ((c = getc(finput)) != 0) {

			if (c == '\\') {
				putc(c, faction);
				c = getc(finput);
				if (c == '\n')
					++lineno;
			} else if (c == match)
				goto lcopy;
			else if (c == '\n')
				error("newline in string or char. const.");
			putc(c, faction);
		}
		error("EOF in string or character constant");

	case EOF:
		error("action does not terminate");

	case '\n':
		++lineno;
		goto lcopy;

	}

      lcopy:
	putc(c, faction);
	goto loop;
}
